package com.hmdp.utils;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;

/**
 * <p>基于redis生成的全局id生成器</p>
 *
 * @author zhuzhenguo
 * @version V1.0
 * @date 2023-01-13 20:15:19
 * @description: 基于redis生成的全局id生成器
 */
@Component
public class RedisIdWorker {

    /**
     * 开始时间戳 2022-01-01 00:00:00
     */
    private static final long BEGIN_TIMESTAMP = 1640995200L;

    /**
     * 序列号位数
     */
    private static final int COUNT_BITS = 32;

    private final StringRedisTemplate stringRedisTemplate;

    /**
     * 在使用构造器的使用能避免注入的依赖是空的情况。因为在bean的生命周期里面先执行的是bean的构造器，然后才给bean里面的属性赋值
     */
    public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * long类型占8个字节，0 0000000-00000000-00000000-00000000 00000000-00000000-00000000-00000000
     * 第一位符号位永远为零表示正数，后31位是从一个时间节点开始的时间戳
     * 最后32位是序列化：秒内的计数器，支持每秒产生2^32个不同ID(一秒钟内不会有超过2^32次方这么大的数)
     * @param keyPrefix 不同的业务传入不同的前缀
     * @return long
     */
    public long nextId(String keyPrefix) {
        // 1.生成时间戳
        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timestamp = nowSecond - BEGIN_TIMESTAMP;
        // 2.生成序列号
        // 2.1 获取当前日期，精确到天
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        // 2.2 自增长(计数器不断递增)，key不存在会自动创建，不用担心空指针问题
        long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
        // 3.拼接并返回 时间戳向左移动32位，剩下的位数都是用0补充了，再将序列号填充到这些位上
        return timestamp << COUNT_BITS | count;
    }
}

